iT邦幫忙

2021 iThome 鐵人賽

DAY 15
1
Modern Web

ZK 30天速成系列 第 15

模組化、重用使用者介面

  • 分享至 

  • xImage
  •  

重用頁面片段

當我們設計頁面發現有些元件的組合重複出現時,雖然可以直接「複製—貼上」,但是如果使用範本插入,會更符合物件導向設計中的「重用」原則 (或 DRY)。而且未來若是範本修改,所有插入範本的頁面也都自然跟著更新,若是原本採用「複製—貼上」,就得一個一個頁面去搜尋修改。

假設我們發現 <textbox> + <button> 這個 pattern 經常出現,就可以把它定義成一個範本並指定一個不重複的名字,光是定義範本並不會顯示在頁面上,而是要用 <apply> 插入。

https://ithelp.ithome.com.tw/upload/images/20210930/20050621uVfss9SjM7.jpg

<apply template="one-field"/>

<template name="one-field">
    <textbox/><button label="送出" style="margin-left: 5px"/>
</template>

範本可以定義在頁面上任何地方,<apply> 都可以透過指定範本名稱來插入,ZK 會將範本內定義的片段插入到 <apply> 的位置。

模組化範本

若我事先在範本內指定幾個 EL 變數,插入的時候根據不同的情境再傳入不同的值的話,可使範本可應用的情境更廣,變得更模組化。

例如我可以把「提示訊息」、「按鈕文字」作為參數輸入,這樣每次使用時該範本時,我都可以調成不同的文字:
https://ithelp.ithome.com.tw/upload/images/20210930/20050621lItCEiWg6T.jpg

<apply template="one-field2" hint="請輸入:" label="OK"/>

<template name="one-field2">
    ${hint}<textbox/><button label="${label}" style="margin-left: 5px"/>
</template>
  • 以上插入範本時傳入兩個參數: hint, label,對應到範本內的2個變數。不過這種方式傳的參數並沒有編譯器幫你檢查名稱或數量,要自己檢查避免打錯字

模式切換

同一份資料有時候有不同的呈現方式,例如個人資料,可以是「顯示模式」或「編輯模式」,就可以透過切換兩種不同的範本來做到模式切換效果。

個人資料編輯

假設我要在呈現英雄的個人資料畫面做出可以立即切換成編輯模式的編輯器:
閱讀模式
https://ithelp.ithome.com.tw/upload/images/20210930/20050621hrPX7QyJsU.jpg

編輯模式
https://ithelp.ithome.com.tw/upload/images/20210930/20050621eBSQ2mZ9LE.jpg

首先先定義兩種範本:normal, edit

<template name="normal">
    <label value="${hero.id}"/>
    <label value="${hero.name}"/>
    <label value="${hero.age}"/>
</template>
<template name="edit">
    <label value="${hero.id}"/>
    <textbox id="nameBox" value="${hero.name}" width="100%"/>
    <intbox id="ageBox" value="${hero.age}"/>
</template>
  • 範本內用 EL 存取 Java bean,兩者都顯示3項相同的 property,只是編輯模式範本內的要改成輸入元件

再透過按鈕去切換不同的範本:

<div  style="width: 50%" apply="quickstart.shadow.ProfileEditorComposer">
    <button label="Edit"/>
    <vlayout style="border: solid 2px; border-radius:5px; width: 150px; padding:5px; margin: 5px">
        <apply id="profile" template="normal" hero="${hero}" dynamicValue="true"/>
    </vlayout>
</div>
  • 因為範本之後要動態被改變,因此 dynamicValue 要設成 true

控制器實作解說

public class ProfileEditorComposer extends SelectorComposer {
    @Wire("::shadow#profile")
    private Apply apply;
  • shadow element 也可以用 @Wire 來取得參考,selector 語法為 ::shadow 代表所有的 shadow 元件,加上 # ID 選擇器,代表 ID 為 profile 的 shadow 元件
@Override
public void doBeforeComposeChildren(Component comp) throws Exception {
    super.doBeforeComposeChildren(comp);
    hero = HeroService.create("奇異博士");
    Sessions.getCurrent().setAttribute("hero", hero);
}
  • doBeforeComposeChildren() 也是一個生命週期方法,根據名稱就可得知它是在子元件被建構前被呼叫。這次我覆寫 doBeforeComposeChildren() 是因為我要在範本中的元件產生前就把資料準備好,這樣 EL 才能存取到 attribute。
  • Sessions.getCurrent() 是 ZK 提供的方法,讓我可以取得現有 request 所屬的 session。請注意我存入的鍵值為 hero,也就是我在 zul 上用 EL 存取的變數名。
<apply id="profile" template="normal" hero="${hero}" dynamicValue="true"/>
  • 這裡用 ${hero} 把參數傳入範本,那這個變數定義在哪呢?ZK 解析 EL 變數時,會從較小的範圍(scope) 一直解析到大的範圍,因此若是 zscript 中沒有定義 hero 這個變數,ZK 會持續在 session, application 中的 attribute 尋找 key 值為 hero 的物件

實作點擊按鈕來切換模式

private boolean isEdit = false;

@Listen("onClick = button")
public void switchMode(){
    if (isEdit){
        hero.setName(((Textbox)modeButton.getFellow("nameBox")).getValue());
        hero.setAge(((Intbox)modeButton.getFellow("ageBox")).getValue());
    }
    isEdit = !isEdit;
    modeButton.setLabel(isEdit ? "Save": "Edit");
    apply.setTemplate(isEdit ? "edit" : "normal");
    apply.recreate();
}
  • @Listen 註冊 onClick 傾聽器
  • 我用 setTemplate() 來切換不同的範本,並呼叫 recreate() 來重建範本內容

頁面佈局

另一種應用是將範本定義為一個佈局範本,然後在每個頁面中插入,可以使得頁面的佈局一致。例如我設計一個由分成標頭、內容、頁尾3部分的佈局範本:

https://ithelp.ithome.com.tw/upload/images/20210930/20050621vdxdAB3Y7u.jpg

layout-template.zul

<borderlayout height="100%">
    <north size="20%" style="background-color:#5c5480">
        <div sclass="center">header</div>
    </north>
    <center style="background-color:#738096">
        <apply template="content"/>
    </center>
    <south size="5%" style="background-color:#5c5480">
        <div sclass="center">footer</div>
    </south>
</borderlayout>
  • 標頭、頁尾都是固定的內容
  • 中間內容沒有定義,插入一個尚未定義的範本 content

當要插入該佈局範本時,才定義 content 這個範本的內容:

<apply templateURI="layout-template.zul">
    <template name="content">
        <div sclass="center">本頁面內容</div>
    </template>
</apply>

這樣就可以做到,既維持頁面在同一個3分佈局,又能彈性決定每個頁面的內容。

以上幾個例子可以幫你更好的重用你的頁面,避免「複製—貼上」這種容易產生 bug 的行為。


上一篇
Shadow Element:控制 UI 元件的元件
下一篇
Shadow Element:條件控制元件的創建、消滅
系列文
ZK 30天速成30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言